package com.wys.spring;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.wys.utils.JsonUtils;
import feign.Client;
import feign.Retryer;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.form.spring.SpringFormEncoder;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
import org.apache.http.conn.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.cloud.openfeign.FeignErrorDecoderFactory;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import org.springframework.cloud.openfeign.loadbalancer.OnRetryNotEnabledCondition;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Configuration
public class OkHttpFeignLoadBalancerConfiguration {


    @Resource
    private ObjectFactory<HttpMessageConverters> objectFactory;

    private static final Logger logger = LoggerFactory.getLogger(OkHttpFeignLoadBalancerConfiguration.class);

    @Bean
    @Primary
    public okhttp3.OkHttpClient okHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
        SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
                .loadTrustMaterial(null, acceptingTrustStrategy)
                .build();
        SkipSslVerificationHttpRequestFactory.SkipX509TrustManager skipX509TrustManager = new SkipSslVerificationHttpRequestFactory.SkipX509TrustManager();
        return new okhttp3.OkHttpClient.Builder().connectTimeout(10000, TimeUnit.MILLISECONDS)
                .connectTimeout(5000, TimeUnit.MILLISECONDS)
                .writeTimeout(10000, TimeUnit.MILLISECONDS)
                .retryOnConnectionFailure(true)
                .hostnameVerifier((x, y) -> true)
                .sslSocketFactory(sslContext.getSocketFactory(), skipX509TrustManager)
                .connectionPool(new ConnectionPool(36, 300, TimeUnit.SECONDS))
                .addInterceptor(new OKHttp3Interceptor()).build();
    }


    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        return JsonUtils.OBJECT_MAPPER;
    }

    @Bean
    @Primary
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        return new MappingJackson2HttpMessageConverter(objectMapper);
    }

    @Bean
    public RestTemplateClientRequestInterceptor restTemplateClientRequestInterceptor(SpringCommonProperties springCommonProperties) {
        return new RestTemplateClientRequestInterceptor(springCommonProperties);
    }

    @Bean
    @Primary
    public InterceptingClientHttpRequestFactory interceptingClientHttpRequestFactory(LoadBalancerClient loadBalancerClient, RestTemplateClientRequestInterceptor restTemplateClientRequestInterceptor, okhttp3.OkHttpClient okHttpClient) {
        List<ClientHttpRequestInterceptor> clientHttpRequestInterceptors = new ArrayList<>();
        clientHttpRequestInterceptors.add(restTemplateClientRequestInterceptor);
        //clientHttpRequestInterceptors.add(new LoadBalancerInterceptor(loadBalancerClient));
        return new InterceptingClientHttpRequestFactory(new OkHttp3ClientHttpRequestFactory(okHttpClient), clientHttpRequestInterceptors);
    }

    @Bean
    @Primary
    public RestTemplate restTemplate(InterceptingClientHttpRequestFactory interceptingClientHttpRequestFactory, MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        RestTemplate restTemplate = new RestTemplate(interceptingClientHttpRequestFactory);
        restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        converterList.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter);
        converterList.add(mappingJackson2HttpMessageConverter);
        return restTemplate;
    }

    @Bean
    public LoadBalancerClientFactory loadBalancerClientFactory() {
        return new LoadBalancerClientFactory();
    }

    @Bean
    public LoadBalancerClient loadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory) {
        return new BlockingLoadBalancerClient(loadBalancerClientFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    @Conditional({OnRetryNotEnabledCondition.class})
    public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
        OkHttpClient delegate = new OkHttpClient(okHttpClient);
        return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
    }

    @Bean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Primary
    public FeignErrorDecoderFactory feign() {
        return type -> new FeignErrorDecoder();

    }

    @Bean
    public Encoder encoder() {
        return new SpringFormEncoder(new SpringEncoder(objectFactory));
    }

    public static class SkipSslVerificationHttpRequestFactory extends SimpleClientHttpRequestFactory {
        private SkipSslVerificationHttpRequestFactory() {
        }

        @Override
        protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
            if (connection instanceof HttpsURLConnection) {
                connection.setConnectTimeout(Integer.MAX_VALUE);
                connection.setReadTimeout(Integer.MAX_VALUE);
                this.prepareHttpsConnection((HttpsURLConnection) connection);
            }

            super.prepareConnection(connection, httpMethod);
        }

        private void prepareHttpsConnection(HttpsURLConnection connection) {
            connection.setHostnameVerifier(new SkipSslVerificationHttpRequestFactory.SkipHostnameVerifier());
            try {
                connection.setSSLSocketFactory(this.createSslSocketFactory());
            } catch (Exception var3) {
            }

        }

        public SSLSocketFactory createSslSocketFactory() throws Exception {
            SSLContext context = SSLContext.getInstance("TLSv1.0");
            context.init(null, new TrustManager[]{new SkipSslVerificationHttpRequestFactory.SkipX509TrustManager()}, new SecureRandom());
            return context.getSocketFactory();
        }

        private static class SkipX509TrustManager implements X509TrustManager {
            private SkipX509TrustManager() {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }
        }

        public static class SkipHostnameVerifier implements HostnameVerifier {
            private SkipHostnameVerifier() {
            }

            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        }
    }
}
